9 - webskraping med python
Når vi skal skrape nettsider, analyserer vi "kildekoden" som ligger bak nettsiden. I de fleste nettleser kan du enkelt se kildekoden ved å høyreklikke på siden og velge "view page source" eller lignende. I denne leksjonen skal vi begynne med å skrape rentebarometeret til Norsk Famileøkonomi. For å se hva vi skal skrape kan du derfor gå til https://www.norskfamilie.no/barometre/rentebarometer/, høyreklikke og velge å se kildekoden.
Elementer er markert i en nettside med såkalte "html-tagger". For eksempel lager du kursiv på en nettside ved å skrive <i>kursiv</i>
. Denne teksten er skrevet i "markdown", som også forstår html-tagger. Om du leser dette interaktivt i en jupyterfil kan du dobbelklikke her og se at denne setningen er skrevet inne i kursivtagger.
Når vi skraper websider er innholdet vi er interessert i veldig ofte inne i en tabell. Det er det her også. Gjør et tekstsøk i kildekoden etter "\<table". Det finnes kun én plass i dokumentet, og markerer begynnelsen på tabellen. Søker du én gang til med "\</table>" finner du hvor tabellen ender.
I mellom disse taggene er det en god del kode som kanskje ser veldig komplisert ut. Men vi trenger kun å forholde oss til følgende tre typer tagger:
<tr>
: rad<th>
: overskrift<td>
: celleFor å hente ut innholdet i tabellen må vi altså søke etter disse taggene, etter at vi har identifisere teksten mellom "tabell"-taggene. Det finnes heldigvis et veldig godt verktøy for dette i python, som heter BeutifulSoup
(pip install beautifulsoup4
i kommandovinduet om det ikke er installert).
Med dette verktøyet kan du enkelt finne de taggene du ønsker. Vi starter med å finne selve tabellen, etter å ha bruke pakken requests
til å laste ned html-filen:
from bs4 import BeautifulSoup
import requests
def fetch_html_tables(url):
"Returns a list of tables in the html of url"
page = requests.get(url)
bs=BeautifulSoup(page.content)
tables=bs.find_all('table')
return tables
tables=fetch_html_tables('https://www.norskfamilie.no/barometre/rentebarometer/')
table_html=tables[0]
#printing top
print(str(table_html)[:1000])
Det vi får ut med bs.find_all('table')
er altså en liste med alle partier i teksten med matchende <table>
-</table>
-tagger. I dette dokumentet er det bare én tabell, så listen har bare ett element. Vi må nå søke videre inne i tabellen etter innholdstaggene. Vi bruker samme funksjon til det. Her er to funksjoner som sammen finner innholdstaggene og returnerer en tabell:
def html_to_table(html):
"Returns the table defined in html as a list"
#defining the table:
table=[]
#iterating over all rows
for row in html.find_all('tr'):
r=[]
#finding all cells in each row:
cells=row.find_all('td')
#if no cells are found, look for headings
if len(cells)==0:
cells=row.find_all('th')
#iterate over cells:
for cell in cells:
cell=format(cell)
r.append(cell)
#append the row to t:
table.append(r)
return table
def format(cell):
"Returns a string after converting bs4 object cell to clean text"
if cell.content is None:
s=cell.text
elif len(cell.content)==0:
return ''
else:
s=' '.join([str(c) for c in cell.content])
#here you can add additional characters/strings you want to
#remove, change punctuations or format the string in other
#ways:
s=s.replace('\xa0','')
s=s.replace('\n','')
return s
table=html_to_table(table_html)
#printing top
print(str(table)[:1000])
Den første funksjonen itererer over tabellceller, mens den andre funksjonen konverterer innholdet fra et bs4-objekt med html-kode til leselig tekst.
Vi har nå skrapet siden, og hentet ut tabellen. For å gjøre den mer leselig, kan vi lagre den som en fil. Når vi lager filer i python bruker vi den innebygde open
-funksjonen. Om vi kaller filen for "rentebarometer.csv", kan vi opprette filen ved å kjøre f=open('rentebarometer.csv','w')
. Strengen 'w'
betyr at vi åpner filen for skriving (writing, i motsetning til lesing/reading markert med 'r'
. Vi fyller filen med innhold med f.write()
.
For å skille kolonnene skal vi her bruke semikolon ';'. Python har en enkel måte å konvertere en liste til en streng med skilletegn. En tar utgangspunkt i skilletegnet, og bruker metoden join()
på det. For eksempel:
';'.join(table[0])
Vi kan nå åpne filen for skriving og iterere over rader og skrive dem til filen.
def save_data(file_name,table):
"Saves table to file_name"
f=open(file_name,'w')
for row in table:
f.write(';'.join(row)+'\n')
f.close()
save_data('rentebarometer.csv',table)
import pandas as pd
pd.read_csv('rentebarometer.csv', delimiter=';')
Vi kan ta en kikk på dataene med Pandas (på windows må du kanskje ta med encoding='latin1'
som argument for å få med æ,ø,å):
from bs4 import BeautifulSoup
import requests
def scrape(url, file_name):
table=[]
tables=fetch_html_tables(url)
#iterate over all tables, if there are more than one:
for tbl in tables:
#exends table so that table is a list containing elements
#from all tables:
table.extend(html_to_table(tbl))
#saving it:
save_data(file_name,table)
return table
Med denne koden kan vi nå skrape hvilken som helst nettside med tabeller vi ønsker å få tak i. For eksempel om vi ønsker å hente timeplanen til kurset:
url=('https://timeplan.uit.no/'
'emne_timeplan.php?sem=24v&fag=&module[]=SOK-1005-1#week-15')
file_name='schedule.csv'
table=scrape(url,file_name)
s='\n'.join(['\t'.join(row) for row in table])
#printing top
print(str(s)[:1000])
Det er ikke alle nettsideeiere som synes det er greit at vi skraper nettsidene deres. For ordens skyld så er det altså helt lovlig å skrape nettsider. Når noen legger ut data på en nettside har de offentliggjort dataene, og kan ikke bestemme hvordan dataene skal leses. Dette gjelder selv om de legger ut beskjed om noe annet.
Det som kan være ulovlig, er å videreformidle dataene.
Når nettsiden er vanskelig å skrape, er selenium av google en veldig nyttig pakke. Med den kan koden din opptre som en vanlig bruker. Så lenge du kan se dataene på skjermen din, bør det da i prinsippet være mulig å skrape enhver side.
Vi har ikke tid til å gå inn på bruken av selenium i dette kurset, men her er en kode som bruker selenium til å skrape nordpool.no. De er ikke spesielt interessert i at vi gjør det, om du forsøker å skrape flere ganger kommer det opp en advarsel om at det er ulovlig, som altså ikke medfører riktighet.
Her er imidlertid en kode som gjøre det mulig å skrape Nordpools nettsider med selenium
1) Finn en side på nettet, som ikke er skrapet her (eller i R-delen av kurset) og skrap den 2) Få siden inn i pandas 3) Gjør noen beregninger du synes er interessante med dataene 4) Lagre tabllen(e)